{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 3.1. 生成课程内容\n",
    "\n",
    "## 🚄 前言\n",
    "将大模型应用于课程制作流程，这是一个非常时髦的技术问题。但是面对陌生的科技主题、不同年龄的学生，我们如何能快速开发出科普课程？本节课程介绍如何利用文本生成模型 Qwen-Max 和图像生成模型 Flux-Merged，为不同年龄的学生自动生成定制化的图文并茂的科普课程脚本。\n",
    "\n",
    "\n",
    "## 🍁 课程目标\n",
    "学完本课程后，你将能够：\n",
    "* 了解 Qwen-Max，并使用 Qwen-Max 生成文本\n",
    "* 了解 Flux-Merged，并使用 Flux-Merged 生成图像\n",
    "\n",
    "\n",
    "## 📖 课程目录\n",
    "\n",
    "- [1. 原理介绍](#🧮-1-原理介绍)\n",
    "- [2. 代码实践](#🛠️-2-代码实践)\n",
    "    - [2.1. 环境准备](#21-环境准备)\n",
    "    - [2.2. 设置 API 客户端](#22-设置-api-客户端)\n",
    "    - [2.3. 生成课程脚本](#23-生成课程脚本)\n",
    "    - [2.4. 生成插画提示词](#24-生成插画提示词)\n",
    "    - [2.5. 生成插画](#25-生成插画)\n",
    "    - [2.6. 合并课程和插图](#26-合并课程和插图)\n",
    "  \n",
    "  \n",
    "## 🧮 1. 原理介绍\n",
    "\n",
    "本次课程用到了以下模型：\n",
    "\n",
    "* [Qwen-Max](https://bailian.console.aliyun.com/#/model-market/detail/qwen-max)：通义千问2.5系列千亿级别超大规模语言模型，支持中文、英文等不同语言输入。随着模型的升级，qwen-max将滚动更新升级。如果希望使用固定版本，请使用历史快照版本。\n",
    "\n",
    "* [Flux-Merged](https://bailian.console.aliyun.com/#/model-market/detail/flux-merged)：FLUX.1-merged模型结合了\"DEV\"在开发阶段探索的深度特性和\"Schnell\"所代表的高速执行优势。通过这一举措，FLUX.1-merged不仅提升了模型的性能界限，还拓宽了其应用范围。\n",
    "\n",
    "结合使用这两种模型生成课程内容的过程如下：\n",
    "\n",
    "<div align=\"center\">\n",
    "    <img src=\"https://gw.alicdn.com/imgextra/i3/O1CN01hj66zt1lp8eTpytmx_!!6000000004867-0-tps-2408-142.jpg\" alt=\"流程\" width=\"90%\"/>\n",
    "</div>\n",
    "\n",
    "\n",
    "## 🛠️ 2. 代码实践\n",
    "\n",
    "接下来，让我们执行以下代码让大模型生成一节课程内容。\n",
    "\n",
    "### 2.1. 环境准备\n",
    "\n",
    "1. 安装 Python 库。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-10-09T05:41:30.565573Z",
     "iopub.status.busy": "2024-10-09T05:41:30.565240Z",
     "iopub.status.idle": "2024-10-09T05:41:37.149391Z",
     "shell.execute_reply": "2024-10-09T05:41:37.148587Z",
     "shell.execute_reply.started": "2024-10-09T05:41:30.565553Z"
    },
    "scrolled": true,
    "tags": []
   },
   "outputs": [],
   "source": [
    "! pip install -r requirements.txt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 导入必要的模块。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-10-09T05:41:37.151423Z",
     "iopub.status.busy": "2024-10-09T05:41:37.151058Z",
     "iopub.status.idle": "2024-10-09T05:41:37.718574Z",
     "shell.execute_reply": "2024-10-09T05:41:37.718018Z",
     "shell.execute_reply.started": "2024-10-09T05:41:37.151391Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "import os\n",
    "import openai\n",
    "from typing import Union\n",
    "import requests\n",
    "import json\n",
    "from http import HTTPStatus\n",
    "from urllib.parse import urlparse, unquote\n",
    "from pathlib import PurePosixPath\n",
    "from dashscope import ImageSynthesis\n",
    "import dashscope\n",
    "from utils import create_directory, save_file,load_config"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. 设置环境变量"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import sys\n",
    "sys.path.append(\"../\")\n",
    "from config.load_key import load_key\n",
    "load_key()\n",
    "print(f'''你配置的 API Key 是：{os.environ[\"DASHSCOPE_API_KEY\"][:5]+\"*\"*5}''')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "4. 加载配置文件。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-10-09T05:41:51.896228Z",
     "iopub.status.busy": "2024-10-09T05:41:51.895874Z",
     "iopub.status.idle": "2024-10-09T05:41:51.900857Z",
     "shell.execute_reply": "2024-10-09T05:41:51.900316Z",
     "shell.execute_reply.started": "2024-10-09T05:41:51.896208Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 加载配置文件代码在 utils.load_config\n",
    "project_config = load_config(\"config.json\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "### 2.2. 设置 API 客户端\n",
    "设置 OpenAI 的 API 客户端，用于后续调用阿里云百炼的 Qwen-Max 模型和 Flux-Merged 模型。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-10-09T05:41:53.141337Z",
     "iopub.status.busy": "2024-10-09T05:41:53.140995Z",
     "iopub.status.idle": "2024-10-09T05:41:53.188464Z",
     "shell.execute_reply": "2024-10-09T05:41:53.187964Z",
     "shell.execute_reply.started": "2024-10-09T05:41:53.141320Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "client = openai.OpenAI(\n",
    "    api_key=os.getenv(\"DASHSCOPE_API_KEY\"),\n",
    "    base_url=\"https://dashscope.aliyuncs.com/compatible-mode/v1\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "### 2.3. 生成课程脚本\n",
    "\n",
    "首先，我们让文本生成模型 Qwen-Max 扮演课程讲师，来生成课程脚本。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. 定义一个 `create_course_script` 函数，用于使用 Qwen-Max 根据给定的主题生成一份适合特定年龄段的课程脚本。它结合了不同的解释级别、创造性和幽默感，以调整课程内容的风格，使其更符合目标受众的需求。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-10-09T05:41:54.423899Z",
     "iopub.status.busy": "2024-10-09T05:41:54.423582Z",
     "iopub.status.idle": "2024-10-09T05:41:54.431400Z",
     "shell.execute_reply": "2024-10-09T05:41:54.430697Z",
     "shell.execute_reply.started": "2024-10-09T05:41:54.423880Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def create_course_script(topic_name, level_of_explanation, age, creativity_level, humour_level):\n",
    "    \"\"\"\n",
    "    根据主题、解释水平、年龄以及创意和幽默水平生成对应的课程脚本。\n",
    "\n",
    "    :param topic_name: str，课程主题的名称。\n",
    "    :param level_of_explanation: str，决定解释的复杂程度，值可以为 '初学者', '中级', '高级'。\n",
    "    :param age: int，授课对象的年龄。\n",
    "    :param creativity_level: int，创意的级别（1-10），决定讲解中使用创造性元素的程度。\n",
    "    :param humour_level: int，幽默的级别（1-10），决定讲解中包含幽默的程度。\n",
    "\n",
    "    :return: str，生成的课程脚本内容，格式化为Markdown。\n",
    "    \"\"\"\n",
    "\n",
    "    # 确保年龄为字符串类型，以便用作提示中的描述\n",
    "    age_str = str(age)\n",
    "\n",
    "    # 定义各级别的解释方式\n",
    "    explanation_levels = {\n",
    "        '初学者': \"用简单易懂的方式解释，帮助读者理解直觉、逻辑和重要性。\",\n",
    "        '中级': \"以更复杂和深入的方式解释，假设读者有一些先前知识和理解。\",\n",
    "        '高级': \"深入讲解主题细节，假设读者有扎实基础，熟悉中级概念。\"\n",
    "    }\n",
    "\n",
    "    # 获取对应级别的解释方式，若未提供则默认为'初学者'\n",
    "    level_string = explanation_levels.get(level_of_explanation, explanation_levels['初学者'])\n",
    "\n",
    "    # 确定创意和幽默的描述\n",
    "    creativity_string = (\n",
    "        \"\" if creativity_level < 4\n",
    "        else \"在解释时富有创意，以便于理解。\" if creativity_level < 7\n",
    "        else \"在解释时富有创意并使用类比。\"\n",
    "    )\n",
    "\n",
    "    humour_string = (\n",
    "        \"\" if humour_level < 4\n",
    "        else \"在解释时带一点幽默。\" if humour_level < 7\n",
    "        else \"在解释时增添笑话，使学习更有趣。\"\n",
    "    )\n",
    "\n",
    "    # 构造提示词，综合所有参数生成课程内容的描述\n",
    "    prompt = (\n",
    "        f\"请生成一个关于{topic_name}的课程内容。\"\n",
    "        f\"授课对象为：{age_str}岁的学生。\"\n",
    "        f\"解释关键概念。\"\n",
    "        f\"课程内容要求：{level_string}{creativity_string}{humour_string}。\"\n",
    "        f\"输出应为Markdown格式。\"\n",
    "        f\"只生成 # 和 ## 两层目录结构，其他都作为要点内容\"\n",
    "        f\"除了课程内容本身，不要输出其他内容\"\n",
    "    )\n",
    "\n",
    "    # 直接调用 API 获取课程脚本\n",
    "    completion = client.chat.completions.create(\n",
    "        model=\"qwen-max\",\n",
    "        messages=[\n",
    "            {\"role\": \"system\", \"content\": \"你是一名专注于科学普及的课程讲师，负责为学生提供清晰的课程内容\"},\n",
    "            {\"role\": \"user\", \"content\": prompt},\n",
    "        ],\n",
    "        stream=True\n",
    "    )\n",
    "\n",
    "    # 解析 API 返回结果，提取生成的课程脚本内容\n",
    "    generated_content = \"\"\n",
    "    for chunk in completion:\n",
    "        chunk_content = chunk.choices[0].delta.content\n",
    "        generated_content += chunk_content\n",
    "        print(chunk_content, end=\"\")\n",
    "\n",
    "    # 返回生成的课程脚本内容\n",
    "    return generated_content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 调用 `create_course_script` 函数根据 `config.json` 中的配置生成课程脚本。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-10-09T05:41:56.982061Z",
     "iopub.status.busy": "2024-10-09T05:41:56.981709Z",
     "iopub.status.idle": "2024-10-09T05:42:48.546275Z",
     "shell.execute_reply": "2024-10-09T05:42:48.545783Z",
     "shell.execute_reply.started": "2024-10-09T05:41:56.982036Z"
    },
    "scrolled": true,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 读取配置文件中的课程内容信息\n",
    "title = project_config[\"title\"]\n",
    "level_of_explanation = project_config[\"level_of_explanation\"]\n",
    "age = project_config[\"age\"]\n",
    "creativity_level = project_config[\"creativity_level\"]\n",
    "humour_level = project_config[\"humour_level\"]\n",
    "\n",
    "# 调用函数生成课程脚本\n",
    "script = create_course_script(title, level_of_explanation, age, creativity_level, humour_level)\n",
    "\n",
    "# 读取配置文件中的课程脚本路径\n",
    "course_file_path = project_config[\"course_file_path\"].format(title=project_config[\"title\"])\n",
    "\n",
    "# 调用函数保存课程脚本\n",
    "save_file(script, course_file_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "### 2.4. 生成插画提示词\n",
    "\n",
    "接下来，我们让 Qwen-Max 模型扮演插画提示词生成专家，为我们的课程脚本生成适配的插画提示词。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. 定义一个 `create_illustration_prompt ` 函数，用于根据给定主题生成适合微课程的多幅插图详细信息。该函数使用 Qwen-Max 模型，通过结构化提示词指导模型，确保插图在主题、描述、场景和风格上精准而富有创意。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-10-09T05:43:52.951806Z",
     "iopub.status.busy": "2024-10-09T05:43:52.951517Z",
     "iopub.status.idle": "2024-10-09T05:43:52.956561Z",
     "shell.execute_reply": "2024-10-09T05:43:52.956066Z",
     "shell.execute_reply.started": "2024-10-09T05:43:52.951787Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def create_illustration_prompt(script):\n",
    "    \"\"\"\n",
    "    根据给定的脚本生成插图提示，并通过 API 获取详细插图信息。\n",
    "\n",
    "    :param script: str，微课程的主题或内容脚本，用于生成插图提示。\n",
    "\n",
    "    :return: str，生成的插图提示，包含详细的插图信息和格式的 JSON 字符串。\n",
    "    \"\"\"\n",
    "\n",
    "    # 系统消息，指定模型的角色和任务\n",
    "    system_message = (\n",
    "        \"您是一位插图提示生成专家，专注于为微课程生成详细的插图提示。\"\n",
    "    )\n",
    "\n",
    "    # 构建插图生成提示\n",
    "    prompt = f\"\"\"生成关于 {script} 的插图。返回仅包含多个插图详细信息的 JSON 数组。\n",
    "    ### 改进指南:\n",
    "    - **主题:** 中心概念。\n",
    "    - **描述:** 详细叙述重点元素、情感和氛围。\n",
    "    - **场景:** 特定环境（如自然、城市、太空），包括颜色、光线和情绪。\n",
    "    - **对象:** 主要主题和特征（如人、动物、物体）。\n",
    "    - **动作:** 对象的动态（如飞行、跳跃、闲逛）。\n",
    "    - **风格:** 艺术技巧（如抽象、超现实主义、水彩、矢量）。\n",
    "    - **细节:** 其他特定信息（如纹理、背景元素）。\n",
    "    ### 生成的提示结构:\n",
    "    “描述, 场景, 包含对象, 动作. 以风格呈现, 强调细节。”\n",
    "    ### 提示示例\n",
    "    [\n",
    "        {{\n",
    "            \"插图编号\": 1,\n",
    "            \"插图位置\": \"第一课\",\n",
    "            \"图片标题\": \"阳光明媚的日子\",\n",
    "            \"插图描述\": \"一幅富有创意的数字艺术作品，描绘了一只由埃菲尔铁塔构建的长颈鹿。\"\n",
    "        }},\n",
    "        {{\n",
    "            \"插图编号\": 2,\n",
    "            \"插图位置\": \"第二课\",\n",
    "            \"图片标题\": \"繁星之夜\",\n",
    "            \"插图描述\": \"一幅黑暗幻想肖像，呈现了一匹马奔跑在风暴中，背景火焰般的景观。\"\n",
    "        }}\n",
    "    ]\n",
    "    输出格式为JSON。不包含任何额外的文字、解释或评论。\"\"\"\n",
    "\n",
    "    # 调用 API 获取插图数据\n",
    "    completion = client.chat.completions.create(\n",
    "        model=\"qwen-max\",\n",
    "        messages=[\n",
    "            {\"role\": \"system\", \"content\": system_message},\n",
    "            {\"role\": \"user\", \"content\": prompt},\n",
    "        ],\n",
    "        stream=True\n",
    "    )\n",
    "\n",
    "    # 解析 API 返回结果\n",
    "    generated_content = \"\"\n",
    "    for chunk in completion:\n",
    "        chunk_content = chunk.choices[0].delta.content\n",
    "        generated_content += chunk_content\n",
    "        print(chunk_content, end=\"\")\n",
    "\n",
    "    print(\"生成的插画提示词:\", generated_content)  # 打印生成的提示\n",
    "\n",
    "    # 返回生成的插图提示\n",
    "    return generated_content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 调用 `create_illustration_prompt` 函数生成插画提示词。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2024-10-09T05:43:54.875500Z",
     "iopub.status.busy": "2024-10-09T05:43:54.875173Z",
     "iopub.status.idle": "2024-10-09T05:44:50.822345Z",
     "shell.execute_reply": "2024-10-09T05:44:50.821859Z",
     "shell.execute_reply.started": "2024-10-09T05:43:54.875482Z"
    },
    "scrolled": true,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 读取配置文件中的插图提示词文件路径\n",
    "illustrations_prompt_file_path = project_config[\"illustrations_prompt_file_path\"].format(title=project_config[\"title\"])\n",
    "\n",
    "# 调用函数生成插画提示词\n",
    "illustrations_prompt = create_illustration_prompt(script)\n",
    "\n",
    "# 调用函数保存插画提示词\n",
    "save_file(illustrations_prompt, illustrations_prompt_file_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "### 2.5. 生成插画"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "使用 Flux-Merged 模型为课程生成插画。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. 定义一个 `generate_illustration ` 函数，用于使用 Flux-Merged 模型根据插图详细信息生成插图。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-10-09T05:45:41.031563Z",
     "iopub.status.busy": "2024-10-09T05:45:41.031231Z",
     "iopub.status.idle": "2024-10-09T05:45:41.036714Z",
     "shell.execute_reply": "2024-10-09T05:45:41.036187Z",
     "shell.execute_reply.started": "2024-10-09T05:45:41.031544Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def generate_illustration(prompt, illustration_number, illustration_title):\n",
    "    \"\"\"\n",
    "    根据给定提示生成插图，并将其保存为文件。\n",
    "\n",
    "    :param prompt: str，描述生成图像的提示信息。\n",
    "    :param illustration_number: int，指明插图的顺序编号，用于命名文件。\n",
    "    :param illustration_title: str，插图的标题，用于命名文件。\n",
    "\n",
    "    :return: str或None，返回生成的图像文件路径；如果生成或保存失败，返回 None。\n",
    "    \"\"\"\n",
    "\n",
    "    # 调用生成图像的函数，使用指定的模型并设置生成图像的大小\n",
    "    rsp = ImageSynthesis.call(model=\"flux-merged\",\n",
    "                               prompt=prompt,\n",
    "                               size='1024*1024')\n",
    "\n",
    "    # 创建保存图像的目录\n",
    "    output_dir = project_config[\"illustrations_file_folder\"]\n",
    "    os.makedirs(output_dir, exist_ok=True)  # 确保目录存在，以便保存生成的图像\n",
    "\n",
    "    # 获取项目的标题用于命名文件\n",
    "    title = project_config[\"title\"]\n",
    "\n",
    "    # 检查生成结果是否为空\n",
    "    if not rsp.output.results:\n",
    "        print(f\"生成插图失败: {illustration_title}, 返回结果为空\")\n",
    "        return None  # 返回 None，表示生成失败\n",
    "\n",
    "    # 假设处理第一张生成的插图\n",
    "    result = rsp.output.results[0]  # 获取生成的第一张图像的结果\n",
    "    # 构造保存文件的名称\n",
    "    file_name = f\"{title}_{illustration_number}_{illustration_title}.png\"\n",
    "    # 使用指定的输出目录和文件名构建完整的文件路径\n",
    "    file_path = os.path.join(output_dir, file_name)\n",
    "\n",
    "    # 尝试下载并保存生成的图像\n",
    "    try:\n",
    "        with open(file_path, 'wb+') as f:\n",
    "            f.write(requests.get(result.url).content)  # 从 URL 下载图像内容并写入文件\n",
    "    except Exception as e:\n",
    "        print(f\"保存图像失败: {file_name}, 错误信息: {e}\")\n",
    "        return None  # 返回 None，表示保存失败\n",
    "\n",
    "    return file_path  # 返回生成的图片文件路径"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 定义一个 `generate_illustrations` 函数用于处理多个插图的生成。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-10-09T05:45:42.181965Z",
     "iopub.status.busy": "2024-10-09T05:45:42.181636Z",
     "iopub.status.idle": "2024-10-09T05:45:42.186983Z",
     "shell.execute_reply": "2024-10-09T05:45:42.186485Z",
     "shell.execute_reply.started": "2024-10-09T05:45:42.181947Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "from IPython.display import Image, display\n",
    "\n",
    "def generate_illustrations(illustrations):\n",
    "    \"\"\"\n",
    "    生成插图并返回更新后的插图信息的 JSON 格式文本。\n",
    "\n",
    "    :param illustrations: JSON 字符串或包含插图信息的列表\n",
    "    :return: 返回更新后的插图信息的 JSON 文本\n",
    "    \"\"\"\n",
    "    output_dir = project_config[\"illustrations_file_folder\"]\n",
    "\n",
    "    # 检查输入类型\n",
    "    if isinstance(illustrations, str):\n",
    "        illustrations = json.loads(illustrations)\n",
    "    elif not isinstance(illustrations, list):\n",
    "        raise ValueError(\"illustrations 需要是一个 JSON 字符串或列表\")\n",
    "\n",
    "    results = []\n",
    "\n",
    "    # 批量调用生成插图的功能\n",
    "    for item in illustrations:\n",
    "        illustration_number = item['插图编号']\n",
    "        illustration_location = item['插图位置']\n",
    "        illustration_title = item['图片标题']\n",
    "        prompt = item['插图描述']\n",
    "\n",
    "        # 调用生成插图的函数\n",
    "        print(f'正在生成插图，编号: {illustration_number}，提示词：{prompt}')\n",
    "        image_path = generate_illustration(prompt, illustration_number, illustration_title)\n",
    "\n",
    "        # 检查生成插图是否成功\n",
    "        if image_path:  # 如果生成成功\n",
    "            print(f\"生成插图成功: 编号: {illustration_number}, 标题: {illustration_title}, 保存路径: {image_path}\")\n",
    "            # 在 notebook 中显示图片，生产环境可能需要修改为保存到对象存储 OSS 等存储介质中\n",
    "            display(Image(filename=image_path, width=128))\n",
    "            # 更新插图路径，直接使用生成的完整路径\n",
    "            item['插图路径'] = image_path  # 直接赋值生成的路径\n",
    "\n",
    "            # 将更新后的插图信息添加到结果列表\n",
    "            results.append(item)\n",
    "        else:\n",
    "            print(f\"插图生成失败: 编号: {illustration_number}, 标题: {illustration_title}\")\n",
    "\n",
    "    # 将结果列表转换为 JSON 格式的字符串\n",
    "    final_result = json.dumps(results, ensure_ascii=False, indent=4)  # 确保中文字符正常显示\n",
    "\n",
    "    print(\"生成的插图信息:\", final_result)\n",
    "    return final_result  # 返回结果的 JSON 文本"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "3. 调用 `generate_illustrations` 函数，生成所有插画并将插画信息保存为 JSON 文件。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecutionIndicator": {
     "show": false
    },
    "execution": {
     "iopub.execute_input": "2024-10-09T05:45:44.224620Z",
     "iopub.status.busy": "2024-10-09T05:45:44.224295Z",
     "iopub.status.idle": "2024-10-09T05:47:19.446055Z",
     "shell.execute_reply": "2024-10-09T05:47:19.445529Z",
     "shell.execute_reply.started": "2024-10-09T05:45:44.224596Z"
    },
    "scrolled": true,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 读取配置文件中的课程插画信息文件路径\n",
    "illustrations_info_file_path = project_config[\"illustrations_info_file_path\"].format(title=project_config[\"title\"])\n",
    "\n",
    "# 生成插图和插画信息\n",
    "illustrations_info = generate_illustrations(illustrations_prompt)\n",
    "\n",
    "# 调用函数保存插画信息\n",
    "save_file(illustrations_info, illustrations_info_file_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "### 2.6. 合并课程和插图\n",
    "\n",
    "最后，我们使用 Qwen-Max 将课程脚本和插画合并，生成一篇图文并茂的科普课程脚本。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1. 定义一个 `add_illustration_to_course_script` 函数，使用 Qwen-Max 将课程脚本和插图合并。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-10-09T05:47:34.046506Z",
     "iopub.status.busy": "2024-10-09T05:47:34.046176Z",
     "iopub.status.idle": "2024-10-09T05:47:34.051566Z",
     "shell.execute_reply": "2024-10-09T05:47:34.050861Z",
     "shell.execute_reply.started": "2024-10-09T05:47:34.046488Z"
    },
    "tags": []
   },
   "outputs": [],
   "source": [
    "def add_illustration_to_course_script(script, course_script_with_illustrations_file_path, illustrations):\n",
    "    \"\"\"\n",
    "    将插图整合到课程脚本中，并返回带插图的Markdown格式课程脚本。\n",
    "\n",
    "    参数：\n",
    "    - script: str，原始课程脚本，包含文本内容。\n",
    "    - course_script_with_illustrations_file_path: str，保存生成课程脚本的文件路径，用于提供上下文。\n",
    "    - illustrations: list，插图列表，包含要插入的图像信息。\n",
    "\n",
    "    返回：\n",
    "    - str，更新后的带插图的课程脚本，格式为Markdown。\n",
    "    \"\"\"\n",
    "\n",
    "    # 创建系统消息，指示助手的角色和任务\n",
    "    system_message = \"你是一名助手，专注于将插画插入课程脚本中。\"\n",
    "\n",
    "    # 计算插图目录的路径，以便后续在提示词中使用\n",
    "    illustrations_dir = \"../illustrations/\"\n",
    "\n",
    "    # 构建提示词，要求模型在指定位置整合插图\n",
    "    prompt = (\n",
    "        \"请在相关位置将插图整合到课程脚本中。\"\n",
    "        \"输出的带插画的课程脚本保持Markdown格式。\"\n",
    "        f\"根据文件路径 {course_script_with_illustrations_file_path}，\"\n",
    "        f\"请将插图的相对路径调整为 {illustrations_dir}\"\n",
    "        \"。请勿包含任何额外的解释。\\n\\n\"\n",
    "        f\"课程脚本:\\n{script}\\n\\n\"\n",
    "        f\"插图列表:\\n{illustrations}\"\n",
    "    )\n",
    "\n",
    "    # 调用 API 获取插图数据，与模型进行交互以生成带插图的课程脚本\n",
    "    completion = client.chat.completions.create(\n",
    "        model=\"qwen-max\",\n",
    "        messages=[\n",
    "            {\"role\": \"system\", \"content\": system_message},\n",
    "            {\"role\": \"user\", \"content\": prompt},\n",
    "        ],\n",
    "        stream=True\n",
    "    )\n",
    "\n",
    "    # 解析 API 返回的结果，确保可以读取生成的内容\n",
    "    generated_content = \"\"\n",
    "    print(\"生成带插画的课程脚本:\")\n",
    "    for chunk in completion:\n",
    "        chunk_content = chunk.choices[0].delta.content\n",
    "        generated_content += chunk_content\n",
    "        print(chunk_content, end=\"\")\n",
    "\n",
    "    # 返回生成的包含插图的课程脚本\n",
    "    return generated_content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "2. 调用 `add_illustration_to_course_script` 函数生成图文并茂的科普课程脚本。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "ExecutionIndicator": {
     "show": true
    },
    "execution": {
     "iopub.execute_input": "2024-10-09T05:47:35.549483Z",
     "iopub.status.busy": "2024-10-09T05:47:35.549136Z",
     "iopub.status.idle": "2024-10-09T05:48:27.036129Z",
     "shell.execute_reply": "2024-10-09T05:48:27.035642Z",
     "shell.execute_reply.started": "2024-10-09T05:47:35.549464Z"
    },
    "scrolled": true,
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 读取配置文件中的带插画的课程文件路径\n",
    "course_script_with_illustrations_file_path = project_config[\"course_script_with_illustrations_file_path\"].format(title=project_config[\"title\"])\n",
    "\n",
    "# 生成带插图的课程脚本\n",
    "course_script_with_illustrations = add_illustration_to_course_script(script, course_script_with_illustrations_file_path, illustrations_info)\n",
    "\n",
    "# 调用函数保存带插图的课程脚本\n",
    "save_file(course_script_with_illustrations, course_script_with_illustrations_file_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "tags": []
   },
   "source": [
    "## ✅ 本节小结\n",
    "\n",
    "- 在本次学习和实践中，我们了解了 Qwen-Max 和 Flux-Merged，并使用这些大模型生成了课程内容。\n",
    "- 这些课程内容得到广泛好评，甚至有机会推广到其他国家的学生，从而引发了国际化的需求。接下来，我们将学习如何使用大模型将课程内容翻译成不同语言，以满足更多学生的需求。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 🔥 课后小测验\n",
    "\n",
    "【多选题】3.1.1. 针对如下课程脚本片段：“学习率是深度学习中重要的超参数。” 你希望使用 Qwen-Max 生成一个合适的“文生图”插图提示词，以下哪个提示词最佳？（ ）\n",
    "\n",
    "A. 学习率\n",
    "\n",
    "B. 深度学习\n",
    "\n",
    "C. 一个图表，展示不同学习率对模型训练的影响\n",
    "\n",
    "D. 超参数\n",
    "\n",
    "答案：C\n",
    "\n",
    "解析： 选项 C 提供了清晰的图像描述，包含了图表类型和学习率的核心概念，能够引导“文生图”生成符合要求的插图。其他选项过于简单，缺乏足够的指导信息。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ✉️ 评价反馈\n",
    "感谢你学习阿里云大模型ACP认证课程，如果你觉得课程有哪里写得好、哪里写得不好，期待你[通过问卷提交评价和反馈](https://survey.aliyun.com/apps/zhiliao/Mo5O9vuie)。\n",
    "\n",
    "你的批评和鼓励都是我们前进的动力。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "venv",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.15"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
